import os
import io
import sys
import json
import yaml
import shutil
import zipfile
import threading
import cv2
import math
import traceback
import posixpath
from PIL import Image as PILImage
from datetime import datetime
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app, send_file, Response
from models import db, Project, Image, Label, Annotation, ExportRecord, LLMConfig
from werkzeug.utils import secure_filename

# 创建蓝图
main = Blueprint('main', __name__)

# 添加导入语句
from inference_manager import InferenceManager

# 动态导入服务模块
import importlib.util

# 导入训练服务
training_service_spec = importlib.util.spec_from_file_location(
    "training_service", 
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "services", "training_service.py")
)
training_service = importlib.util.module_from_spec(training_service_spec)
training_service_spec.loader.exec_module(training_service)

# 导入数据集服务
dataset_service_spec = importlib.util.spec_from_file_location(
    "dataset_service", 
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "services", "dataset_service.py")
)
dataset_service = importlib.util.module_from_spec(dataset_service_spec)
dataset_service_spec.loader.exec_module(dataset_service)

# 导入标注服务
annotation_service_spec = importlib.util.spec_from_file_location(
    "annotation_service", 
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "services", "annotation_service.py")
)
annotation_service = importlib.util.module_from_spec(annotation_service_spec)
annotation_service_spec.loader.exec_module(annotation_service)

# 导出服务
export_service_spec = importlib.util.spec_from_file_location(
    "export_service", 
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "services", "export_service.py")
)
export_service = importlib.util.module_from_spec(export_service_spec)
export_service_spec.loader.exec_module(export_service)

# 摄像头服务
camera_service_spec = importlib.util.spec_from_file_location(
    "camera_service", 
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "services", "camera_service.py")
)
camera_service = importlib.util.module_from_spec(camera_service_spec)
camera_service_spec.loader.exec_module(camera_service)

# 图片服务
image_service_spec = importlib.util.spec_from_file_location(
    "image_service", 
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "services", "image_service.py")
)
image_service = importlib.util.module_from_spec(image_service_spec)
image_service_spec.loader.exec_module(image_service)

# 初始化服务
training_service_instance = training_service.TrainingService()
dataset_service_instance = dataset_service.DatasetService()
annotation_service_instance = annotation_service.AnnotationService()
export_service_instance = export_service.ExportService()
camera_service_instance = camera_service.CameraService()
image_service_instance = image_service.ImageService()

# 全局字典用于管理RTSP流状态
rtsp_streams = {}


@main.route('/')
def index():
    projects = Project.query.order_by(Project.created_at.desc()).all()
    return render_template('index.html', projects=projects)

@main.route('/project/create', methods=['POST'])
def create_project():
    name = request.form.get('name')
    description = request.form.get('description')
    
    if not name:
        flash('项目名称不能为空', 'error')
        return redirect(url_for('main.index'))
    
    project = Project(name=name, description=description)
    db.session.add(project)
    db.session.commit()
    
    flash(f'项目 "{name}" 创建成功', 'success')
    return redirect(url_for('main.project_detail', project_id=project.id))

@main.route('/project/<int:project_id>')
def project_detail(project_id):
    project = Project.query.get_or_404(project_id)
    return render_template('project_detail.html', project=project)

@main.route('/project/<int:project_id>/delete', methods=['POST'])
def delete_project(project_id):
    project = Project.query.get_or_404(project_id)
    project_name = project.name
    
    # 删除项目相关的所有文件
    project_path = os.path.join('static/datasets', str(project_id))
    if os.path.exists(project_path):
        shutil.rmtree(project_path)
    
    # 删除项目记录
    db.session.delete(project)
    db.session.commit()
    
    flash(f'项目 "{project_name}" 已删除', 'success')
    return redirect(url_for('main.index'))

# 图片管理
@main.route('/project/<int:project_id>/images')
def project_images(project_id):
    project = Project.query.get_or_404(project_id)
    
    # 获取分页参数
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 50, type=int)
    
    # 确保per_page在合理范围内
    per_page = min(per_page, 100)  # 最多显示100张图片
    
    # 分页查询图片
    images_pagination = Image.query.filter_by(project_id=project_id).paginate(
        page=page, per_page=per_page, error_out=False)
    
    images = images_pagination.items
    
    # 为每个图片预加载标注信息，确保模板中可以正确访问
    for image in images:
        _ = image.annotations  # 触发加载
    
    return render_template('images.html', project=project, images=images, pagination=images_pagination)

@main.route('/project/<int:project_id>/image/<int:image_id>/delete', methods=['POST'])
def delete_image(project_id, image_id):
    project = Project.query.get_or_404(project_id)
    image = Image.query.get_or_404(image_id)
    
    # 确保图片属于该项目
    if image.project_id != project_id:
        flash('无效的图片ID', 'error')
        return redirect(url_for('main.project_images', project_id=project_id))
    
    # 获取图片文件路径
    image_path = os.path.join(current_app.root_path, image.path)
    
    try:
        # 删除数据库记录
        db.session.delete(image)
        db.session.commit()
        
        # 删除图片文件
        if os.path.exists(image_path):
            os.remove(image_path)
            
        # 删除对应的YOLO格式标注文件（.txt文件）
        txt_file_path = os.path.splitext(image_path)[0] + '.txt'
        if os.path.exists(txt_file_path):
            os.remove(txt_file_path)
        
        flash(f'图片 "{image.original_filename}" 已删除', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'删除图片时出错: {str(e)}', 'error')
    
    return redirect(url_for('main.project_images', project_id=project_id))

@main.route('/project/<int:project_id>/upload_images', methods=['POST'])
def upload_images(project_id):
    project = Project.query.get_or_404(project_id)
    
    # 处理ZIP文件上传
    if 'zip_file' in request.files:
        zip_file = request.files['zip_file']
        if zip_file.filename != '':
            # 保存ZIP文件
            zip_path = os.path.join('static/uploads', f"{project_id}_{zip_file.filename}")
            zip_file.save(zip_path)
            
            # 解压ZIP文件
            with zipfile.ZipFile(zip_path, 'r') as zip_ref:
                extract_path = os.path.join('static/uploads', str(project_id))
                os.makedirs(extract_path, exist_ok=True)
                zip_ref.extractall(extract_path)
            
            # 删除ZIP文件
            os.remove(zip_path)
            
            # 遍历解压后的文件并添加到数据库
            for root, dirs, files in os.walk(extract_path):
                for file in files:
                    if file.lower().endswith(('.png', '.jpg', '.jpeg')):
                        file_path = os.path.join(root, file)
                        relative_path = os.path.relpath(file_path, 'static')
                        # 使用posixpath处理URL路径，确保在Windows上也使用正斜杠
                        relative_path = posixpath.join(*relative_path.split(os.sep))
                        img = PILImage.open(file_path)
                        width, height = img.size
                        
                        image = Image(
                            filename=file,
                            original_filename=file,
                            path=relative_path,
                            project_id=project_id,
                            width=width,
                            height=height
                        )
                        db.session.add(image)
            
            db.session.commit()
            flash('ZIP文件上传并解压成功', 'success')
            return redirect(url_for('main.project_images', project_id=project_id))
    
    # 处理单个或多个图片上传
    if 'images' in request.files:
        images = request.files.getlist('images')
        for image_file in images:
            if image_file.filename != '':
                # 保存图片
                filename = f"{project_id}_{int(datetime.now().timestamp()*1000)}_{image_file.filename}"
                file_path = os.path.join('static/uploads', filename)
                image_file.save(file_path)
                
                # 获取图片尺寸
                img = PILImage.open(file_path)
                width, height = img.size
                
                # 保存到数据库，确保路径正确
                relative_path = os.path.join('uploads', filename)
                # 使用posixpath处理URL路径，确保在Windows上也使用正斜杠
                relative_path = posixpath.join(*relative_path.split(os.sep))
                image = Image(
                    filename=filename,
                    original_filename=image_file.filename,
                    path=relative_path,
                    project_id=project_id,
                    width=width,
                    height=height
                )
                db.session.add(image)
        
        db.session.commit()
        flash('图片上传成功', 'success')
    
    return redirect(url_for('main.project_images', project_id=project_id))

@main.route('/project/<int:project_id>/delete_selected_images', methods=['POST'])
def delete_selected_images(project_id):
    """批量删除选中的图片"""
    project = Project.query.get_or_404(project_id)
    
    try:
        # 获取选中的图片ID列表
        image_ids_json = request.form.get('image_ids', '[]')
        image_ids = json.loads(image_ids_json)
        
        if not image_ids:
            flash('未选择任何图片', 'warning')
            return redirect(url_for('main.project_images', project_id=project_id))
        
        # 使用图片服务删除选中的图片
        deleted_count, errors = image_service_instance.delete_images(image_ids)
        
        if errors:
            for error in errors:
                flash(error, 'error')
        
        if deleted_count > 0:
            flash(f'成功删除 {deleted_count} 张图片', 'success')
        elif not errors:
            flash('未删除任何图片', 'warning')
            
    except Exception as e:
        flash(f'批量删除图片时出错: {str(e)}', 'error')
    
    return redirect(url_for('main.project_images', project_id=project_id))

@main.route('/project/<int:project_id>/delete_unannotated_images', methods=['POST'])
def delete_unannotated_images(project_id):
    """删除项目中所有未标注的图片"""
    project = Project.query.get_or_404(project_id)
    
    try:
        # 使用图片服务删除所有未标注的图片
        deleted_count, errors = image_service_instance.delete_unannotated_images(project_id)
        
        if errors:
            for error in errors:
                flash(error, 'error')
        
        if deleted_count > 0:
            flash(f'成功删除 {deleted_count} 张未标注的图片', 'success')
        elif deleted_count == 0:
            flash('没有未标注的图片需要删除', 'info')
            
    except Exception as e:
        flash(f'删除未标注图片时出错: {str(e)}', 'error')
    
    return redirect(url_for('main.project_images', project_id=project_id))

# 标注功能
@main.route('/project/<int:project_id>/annotate')
def annotate(project_id):
    project = Project.query.get_or_404(project_id)
    images = Image.query.filter_by(project_id=project_id).all()
    
    # 如果没有图片，重定向到图片上传页面
    if not images:
        flash('请先上传图片再进行标注', 'warning')
        return redirect(url_for('main.project_images', project_id=project_id))
    
    # 默认显示第一张图片
    image_id = request.args.get('image_id', images[0].id)
    current_image = Image.query.get_or_404(image_id)
    
    return render_template('annotate.html', 
                         project=project, 
                         images=images, 
                         current_image=current_image)

@main.route('/project/<int:project_id>/annotate/<int:image_id>')
def annotate_image(project_id, image_id):
    project = Project.query.get_or_404(project_id)
    images = Image.query.filter_by(project_id=project_id).all()
    current_image = Image.query.get_or_404(image_id)
    
    return render_template('annotate.html', 
                         project=project, 
                         images=images, 
                         current_image=current_image)

import os

@main.route('/api/project/<int:project_id>/labels')
def api_labels(project_id):
    labels_data = annotation_service_instance.get_labels(project_id)
    return jsonify(labels_data)

@main.route('/api/project/<int:project_id>/labels/create', methods=['POST'])
def api_create_label(project_id):
    data = request.get_json()
    name = data.get('name')
    color = data.get('color', '#0066ff')
    
    result = annotation_service_instance.create_label(project_id, name, color)
    return jsonify(result)

@main.route('/api/project/<int:project_id>/labels/<int:label_id>/delete', methods=['POST'])
def api_delete_label(project_id, label_id):
    result = annotation_service_instance.delete_label(label_id)
    return jsonify(result)

@main.route('/api/project/<int:project_id>/labels/<int:label_id>/update', methods=['POST'])
def api_update_label(project_id, label_id):
    data = request.get_json()
    name = data.get('name')
    color = data.get('color')
    
    result = annotation_service_instance.update_label(label_id, name, color)
    return jsonify(result)

@main.route('/api/image/<int:image_id>/annotations')
def api_annotations(image_id):
    annotations_data = annotation_service_instance.get_annotations(image_id)
    return jsonify(annotations_data)

import math

@main.route('/api/image/<int:image_id>/annotations/save', methods=['POST'])
def api_save_annotations(image_id):
    data = request.get_json()
    annotations = data.get('annotations', [])
    
    result = annotation_service_instance.save_annotations(image_id, annotations)
    return jsonify(result)

# 数据集划分
@main.route('/project/<int:project_id>/dataset')
def project_dataset(project_id):
    project = Project.query.get_or_404(project_id)
    
    # 统计各类型数据集数量
    train_count = Image.query.filter_by(project_id=project_id, dataset_type='train').count()
    val_count = Image.query.filter_by(project_id=project_id, dataset_type='val').count()
    test_count = Image.query.filter_by(project_id=project_id, dataset_type='test').count()
    unassigned_count = Image.query.filter_by(project_id=project_id, dataset_type='unassigned').count()
    
    return render_template('dataset.html', 
                         project=project,
                         train_count=train_count,
                         val_count=val_count,
                         test_count=test_count,
                         unassigned_count=unassigned_count)

@main.route('/api/project/<int:project_id>/dataset/update', methods=['POST'])
def api_update_dataset(project_id):
    data = request.get_json()
    image_ids = data.get('image_ids', [])
    dataset_type = data.get('dataset_type')
    
    result = dataset_service_instance.update_dataset(project_id, image_ids, dataset_type)
    return jsonify(result)

@main.route('/api/project/<int:project_id>/dataset/auto_assign', methods=['POST'])
def api_auto_assign_dataset(project_id):
    result = dataset_service_instance.auto_assign_dataset(project_id)
    return jsonify(result)


# 模型训练
@main.route('/project/<int:project_id>/train')
def project_train(project_id):
    project = Project.query.get_or_404(project_id)
    status = training_service_instance.get_training_status(project_id)
    return render_template('train.html', project=project, status=status)

@main.route('/api/project/<int:project_id>/train', methods=['POST'])
def api_start_training(project_id):
    try:
        # 获取训练参数
        data = request.get_json()
        epochs = data.get('epochs', 20)  # 默认20个epochs
        model_arch = data.get('model_arch', 'yolov8n.pt')  # 默认使用yolov8n模型
        img_size = data.get('img_size', 640)  # 默认图像尺寸640
        batch_size = data.get('batch_size', 16)  # 默认批次大小16
        use_gpu = data.get('use_gpu', True)  # 默认使用GPU
        
        result = training_service_instance.start_training(project_id, epochs, model_arch, img_size, batch_size, use_gpu)
        return jsonify(result)
    except Exception as e:
        return jsonify({'success': False, 'message': f'启动训练失败: {str(e)}'})

@main.route('/api/project/<int:project_id>/train/stop', methods=['POST'])
def api_stop_training(project_id):
    result = training_service_instance.stop_training(project_id)
    return jsonify(result)

@main.route('/api/project/<int:project_id>/train/status')
def api_train_status(project_id):
    status = training_service_instance.get_training_status(project_id)
    return jsonify(status)

# 模型导出
@main.route('/project/<int:project_id>/export')
def project_export(project_id):
    project = Project.query.get_or_404(project_id)
    # 获取应用根路径
    root_path = current_app.root_path
    return render_template('export.html', project=project, root_path=root_path)

@main.route('/api/project/<int:project_id>/export/<format>', methods=['POST'])
def api_export_model(project_id, format):
    result = export_service_instance.export_model(project_id, format)
    return jsonify(result)

@main.route('/download/<path:filename>')
def download_file(filename):
    # 构造完整文件路径
    full_path = os.path.join(current_app.root_path, filename)
    
    # 检查文件是否存在
    if not os.path.exists(full_path):
        return "文件不存在", 404
    
    # 确定文件的MIME类型和下载文件名
    mimetype = 'application/octet-stream'
    download_name = os.path.basename(filename)
    
    # 根据文件扩展名设置特定的MIME类型
    if filename.endswith('.onnx'):
        mimetype = 'application/octet-stream'
        download_name = filename.split('/')[-1]  # 确保获取正确的文件名
    elif filename.endswith('.pt'):
        mimetype = 'application/octet-stream'
        download_name = filename.split('/')[-1]
    elif filename.endswith('.torchscript'):
        mimetype = 'application/octet-stream'
        download_name = filename.split('/')[-1]
    
    return send_file(
        full_path, 
        as_attachment=True, 
        mimetype=mimetype,
        download_name=download_name
    )

@main.route('/api/project/<int:project_id>/dataset/download')
def api_download_dataset(project_id):
    try:
        project = Project.query.get_or_404(project_id)
        
        # 检查数据集目录是否存在
        project_dir = os.path.join(current_app.root_path, 'static/datasets', str(project_id))
        if not os.path.exists(project_dir):
            # 如果目录不存在，重新组织一次
            dataset_service_instance.organize_dataset_directories(project_id)
        
        if not os.path.exists(project_dir):
            return jsonify({'success': False, 'message': '数据集目录不存在'}), 404
        
        # 创建临时ZIP文件
        import tempfile
        import zipfile
        
        temp_dir = tempfile.mkdtemp()
        zip_filename = f'dataset_{project_id}.zip'
        zip_filepath = os.path.join(temp_dir, zip_filename)
        
        # 创建ZIP文件
        with zipfile.ZipFile(zip_filepath, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for root, dirs, files in os.walk(project_dir):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.relpath(file_path, project_dir)
                    zipf.write(file_path, arcname)
        
        # 发送ZIP文件
        return send_file(
            zip_filepath,
            as_attachment=True,
            download_name=zip_filename,
            mimetype='application/zip'
        )
        
    except Exception as e:
        print(f"下载数据集时出错: {e}")
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/project/<int:project_id>/rtsp')
def rtsp_capture(project_id):
    """RTSP截图页面"""
    project = Project.query.get_or_404(project_id)
    return render_template('rtsp_capture.html', project=project)



@main.route('/api/project/<int:project_id>/rtsp/start', methods=['POST'])
def api_start_rtsp_capture(project_id):
    """启动RTSP截图"""
    try:
        project = Project.query.get_or_404(project_id)
        data = request.get_json()
        
        rtsp_url = data.get('rtsp_url')
        interval = data.get('interval', 5)
        max_count = data.get('max_count', 10)
        
        result = camera_service_instance.start_rtsp_capture(project_id, rtsp_url, interval, max_count)
        return jsonify(result)
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@main.route('/api/project/<int:project_id>/rtsp/stop', methods=['POST'])
def api_stop_rtsp_capture(project_id):
    """停止RTSP截图"""
    result = camera_service_instance.stop_rtsp_capture(project_id)
    return jsonify(result)

@main.route('/api/project/<int:project_id>/rtsp/status')
def api_rtsp_status(project_id):
    """获取RTSP截图状态"""
    result = camera_service_instance.get_rtsp_status(project_id)
    return jsonify(result)

@main.route('/api/onvif/discover')
def api_discover_onvif_devices():
    """发现ONVIF设备"""
    result = camera_service_instance.discover_onvif_devices()
    return jsonify(result)

@main.route('/api/onvif/<device_ip>/<device_port>/profiles', methods=['POST'])
def api_get_onvif_profiles(device_ip, device_port):
    """获取ONVIF设备的配置文件列表"""
    try:
        data = request.get_json()
        username = data.get('username')
        password = data.get('password')
        
        if not username or not password:
            return jsonify({'success': False, 'message': '用户名和密码不能为空'})
        
        result = camera_service_instance.get_onvif_profiles(device_ip, device_port, username, password)
        return jsonify(result)
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@main.route('/api/project/<int:project_id>/onvif/start', methods=['POST'])
def api_start_onvif_capture(project_id):
    """启动ONVIF截图"""
    try:
        project = Project.query.get_or_404(project_id)
        data = request.get_json()
        
        device_ip = data.get('device_ip')
        device_port = data.get('device_port')
        username = data.get('username')
        password = data.get('password')
        profile_token = data.get('profile_token')
        interval = data.get('interval', 5)
        max_count = data.get('max_count', 10)
        
        result = camera_service_instance.start_onvif_capture(
            project_id, device_ip, device_port, username, password,
            profile_token, interval, max_count
        )
        return jsonify(result)
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@main.route('/api/project/<int:project_id>/onvif/stop', methods=['POST'])
def api_stop_onvif_capture(project_id):
    """停止ONVIF截图"""
    try:
        data = request.get_json()
        profile_token = data.get('profile_token')
        
        if not profile_token:
            return jsonify({'success': False, 'message': '配置文件token不能为空'})
        
        result = camera_service_instance.stop_onvif_capture(project_id, profile_token)
        return jsonify(result)
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@main.route('/api/project/<int:project_id>/onvif/status', methods=['POST'])
def api_onvif_status(project_id):
    """获取ONVIF截图状态"""
    try:
        data = request.get_json()
        profile_token = data.get('profile_token')
        
        if not profile_token:
            return jsonify({'success': False, 'message': '配置文件token不能为空'})
        
        result = camera_service_instance.get_onvif_status(project_id, profile_token)
        return jsonify(result)
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@main.route('/project/<int:project_id>/inference')
def model_inference(project_id):
    """模型推理页面"""
    project = Project.query.get_or_404(project_id)
    
    # 检查系统模型
    inference_manager = InferenceManager(project_id)
    model_status = inference_manager.check_system_models()
    
    # 获取已存在的模型列表（从项目模型目录）
    existing_models = inference_manager.get_existing_models()
    
    # 获取已上传的模型列表（从uploaded_models目录）
    uploaded_models = inference_manager.get_uploaded_models()
    
    return render_template('inference.html', 
                         project=project,
                         best_model_exists=model_status['best_model_exists'],
                         last_model_exists=model_status['last_model_exists'],
                         existing_models=existing_models,
                         uploaded_models=uploaded_models)


@main.route('/project/<int:project_id>/inference/run', methods=['POST'])
def run_inference(project_id):
    """执行模型推理"""
    project = Project.query.get_or_404(project_id)
    
    try:
        # 获取表单数据
        model_type = request.form.get('model_type')
        inference_type = request.form.get('inference_type')
        system_model = request.form.get('system_model')
        uploaded_model = request.form.get('uploaded_model')
        existing_model = request.form.get('existing_model')
        
        # 获取上传的文件
        model_file = request.files.get('model_file')
        image_file = request.files.get('image_file')
        video_file = request.files.get('video_file')
        rtsp_url = request.form.get('rtsp_url')
        
        # 创建推理管理器
        inference_manager = InferenceManager(project_id)
        
        # 加载模型
        if model_type == 'uploaded':
            model = inference_manager.load_model('uploaded', model_file=uploaded_model)
        else:
            model = inference_manager.load_model(model_type, system_model, model_file, existing_model)
        
        # 根据推理类型执行推理
        if inference_type == 'image':
            if not image_file or image_file.filename == '':
                return jsonify({
                    'success': False,
                    'error': '未选择图片文件'
                })
            
            result = inference_manager.inference_image(model, image_file)
            return jsonify({
                'success': True,
                'result': result
            })
            
        elif inference_type == 'video':
            if not video_file or video_file.filename == '':
                return jsonify({
                    'success': False,
                    'error': '未选择视频文件'
                })
            
            result = inference_manager.inference_video(model, video_file)
            return jsonify({
                'success': True,
                'result': result
            })
            
        elif inference_type == 'rtsp':
            if not rtsp_url:
                return jsonify({
                    'success': False,
                    'error': 'RTSP地址不能为空'
                })
            
            result = inference_manager.inference_rtsp(model, rtsp_url)
            return jsonify({
                'success': True,
                'result': result
            })
            
        else:
            return jsonify({
                'success': False,
                'error': f'不支持的推理类型: {inference_type}'
            })
            
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        })


@main.route('/api/project/<int:project_id>/images/delete_selected', methods=['POST'])
def api_delete_selected_images(project_id):
    """删除选中的图片"""
    try:
        project = Project.query.get_or_404(project_id)
        data = request.get_json()
        image_ids = data.get('image_ids', [])
        
        if not image_ids:
            return jsonify({'success': False, 'message': '未提供图片ID'})
        
        # 使用图片服务删除选中的图片
        deleted_count, errors = image_service_instance.delete_images(image_ids)
        
        if errors:
            return jsonify({'success': False, 'message': '; '.join(errors)})
        
        return jsonify({
            'success': True, 
            'deleted_count': deleted_count,
            'message': f'成功删除 {deleted_count} 张图片'
        })
    except Exception as e:
        return jsonify({'success': False, 'message': f'删除图片时出错: {str(e)}'})

@main.route('/api/project/<int:project_id>/images/delete_unannotated', methods=['POST'])
def api_delete_unannotated_images(project_id):
    """删除未标注的图片"""
    try:
        project = Project.query.get_or_404(project_id)
        
        # 使用图片服务删除所有未标注的图片
        deleted_count, errors = image_service_instance.delete_unannotated_images(project_id)
        
        if errors:
            return jsonify({'success': False, 'message': '; '.join(errors)})
        
        return jsonify({
            'success': True, 
            'deleted_count': deleted_count,
            'message': f'成功删除 {deleted_count} 张未标注图片'
        })
    except Exception as e:
        return jsonify({'success': False, 'message': f'删除未标注图片时出错: {str(e)}'})

# RTSP流媒体推理API
@main.route('/project/<int:project_id>/rtsp_stream')
def rtsp_stream_page(project_id):
    """RTSP实时流推理页面"""
    project = Project.query.get_or_404(project_id)
    
    # 检查系统模型
    inference_manager = InferenceManager(project_id)
    model_status = inference_manager.check_system_models()
    
    # 获取已存在的模型列表
    existing_models = inference_manager.get_existing_models()
    
    return render_template('rtsp_stream.html', 
                         project=project,
                         best_model_exists=model_status['best_model_exists'],
                         last_model_exists=model_status['last_model_exists'],
                         existing_models=existing_models)

@main.route('/project/<int:project_id>/rtsp_stream/video_feed')
def rtsp_video_feed(project_id):
    """RTSP实时视频流"""
    from flask import Response
    import cv2
    import threading
    import time
    
    # 在请求上下文中获取参数
    rtsp_url = request.args.get('rtsp_url')
    model_type = request.args.get('model_type', 'system')
    system_model = request.args.get('system_model')
    existing_model = request.args.get('existing_model')
    uploaded_model = request.args.get('uploaded_model')
    
    # 创建流ID
    stream_id = f"{project_id}_{rtsp_url}_{model_type}_{system_model or existing_model or uploaded_model}"
    
    # 停止之前的流（如果存在）
    if stream_id in rtsp_streams:
        print(f"停止之前的流: {stream_id}")
        rtsp_streams[stream_id]['stop_flag'] = True
    
    # 创建新的停止标志
    rtsp_streams[stream_id] = {'stop_flag': False}
    
    # 在请求上下文中创建推理管理器并加载模型
    try:
        print("正在加载模型...")
        inference_manager = InferenceManager(project_id)
        
        # 根据模型类型调用不同的加载方式
        if model_type == 'uploaded':
            model = inference_manager.load_model('uploaded', model_file=uploaded_model)
        else:
            model = inference_manager.load_model(model_type, system_model, uploaded_model, existing_model)
        print("模型加载成功")
    except Exception as e:
        print(f"模型加载失败: {e}")
        # 返回错误响应
        def error_generator():
            error_frame = generate_error_frame(f"模型加载失败: {str(e)}")
            ret, buffer = cv2.imencode('.jpg', error_frame)
            if ret:
                yield (b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
        return Response(error_generator(), mimetype='multipart/x-mixed-replace; boundary=frame')
    
    def generate_frames(rtsp_url, model_type, system_model, existing_model, model, inference_manager, stream_id):
        cap = None
        try:
            
            print(f"RTSP流参数: URL={rtsp_url}, 模型类型={model_type}, 系统模型={system_model}, 现有模型={existing_model}")
            
            if not rtsp_url:
                print("错误: 未提供RTSP URL")
                return
                
            # 模型已在请求上下文中加载
            
            # 打开RTSP流
            print(f"正在连接RTSP流: {rtsp_url}")
            cap = cv2.VideoCapture(rtsp_url)
            
            # 设置连接超时和缓冲区大小
            cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
            cap.set(cv2.CAP_PROP_FPS, 30)
            
            if not cap.isOpened():
                print(f"错误: 无法打开RTSP流 {rtsp_url}")
                # 生成错误图像
                error_frame = generate_error_frame("无法连接RTSP流，请检查地址和网络连接")
                ret, buffer = cv2.imencode('.jpg', error_frame)
                if ret:
                    frame_bytes = buffer.tobytes()
                    yield (b'--frame\r\n'
                           b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
                return
                
            print("RTSP流连接成功，开始处理帧...")
            frame_count = 0
            
            while True:
                # 检查停止标志
                if stream_id in rtsp_streams and rtsp_streams[stream_id]['stop_flag']:
                    print(f"收到停止信号，退出流: {stream_id}")
                    break
                    
                ret, frame = cap.read()
                if not ret:
                    print("警告: 无法读取帧，可能是网络中断")
                    # 生成错误图像
                    error_frame = generate_error_frame("视频流中断，正在尝试重连...")
                    ret, buffer = cv2.imencode('.jpg', error_frame)
                    if ret:
                        frame_bytes = buffer.tobytes()
                        yield (b'--frame\r\n'
                               b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
                    time.sleep(1)  # 等待1秒后重试
                    continue
                    
                frame_count += 1
                if frame_count % 100 == 0:
                    print(f"已处理 {frame_count} 帧")
                    
                # 进行推理
                results = model(frame)
                annotated_frame = results[0].plot()
                
                # 编码为JPEG
                ret, buffer = cv2.imencode('.jpg', annotated_frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
                if not ret:
                    print("警告: 帧编码失败")
                    continue
                    
                frame_bytes = buffer.tobytes()
                
                # 生成多部分响应
                yield (b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
                       
                # 控制帧率
                time.sleep(0.033)  # 约30fps
                
        except Exception as e:
            print(f"RTSP流处理错误: {e}")
            import traceback
            traceback.print_exc()
            
            # 生成错误图像
            try:
                error_frame = generate_error_frame(f"处理错误: {str(e)}")
                ret, buffer = cv2.imencode('.jpg', error_frame)
                if ret:
                    frame_bytes = buffer.tobytes()
                    yield (b'--frame\r\n'
                           b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
            except:
                pass
        finally:
            if cap is not None:
                cap.release()
                print("RTSP流已释放")
            # 清理流状态
            if stream_id in rtsp_streams:
                del rtsp_streams[stream_id]
                print(f"清理流状态: {stream_id}")
    
    return Response(generate_frames(rtsp_url, model_type, system_model, existing_model, model, inference_manager, stream_id),
                   mimetype='multipart/x-mixed-replace; boundary=frame')

@main.route('/api/project/<int:project_id>/rtsp_stream/stop', methods=['POST'])
def api_stop_rtsp_stream(project_id):
    """停止RTSP实时推理流"""
    try:
        data = request.get_json() or {}
        rtsp_url = data.get('rtsp_url', '')
        model_type = data.get('model_type', 'system')
        system_model = data.get('system_model', 'yolov8n')
        existing_model = data.get('existing_model')
        uploaded_model = data.get('uploaded_model')
        
        # 创建流ID（与video_feed中的逻辑一致）
        stream_id = f"{project_id}_{rtsp_url}_{model_type}_{system_model or existing_model or uploaded_model}"
        
        # 设置停止标志
        if stream_id in rtsp_streams:
            rtsp_streams[stream_id]['stop_flag'] = True
            print(f"设置停止标志: {stream_id}")
            return jsonify({'success': True, 'message': '停止信号已发送'})
        else:
            print(f"未找到活动流: {stream_id}")
            return jsonify({'success': True, 'message': '未找到活动流'})
            
    except Exception as e:
        print(f"停止RTSP流错误: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

def generate_error_frame(message):
    """生成错误提示图像"""
    import numpy as np
    
    # 创建黑色背景
    frame = np.zeros((480, 640, 3), dtype=np.uint8)
    
    # 添加文字
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 0.7
    color = (0, 0, 255)  # 红色
    thickness = 2
    
    # 计算文字位置
    text_size = cv2.getTextSize(message, font, font_scale, thickness)[0]
    text_x = (frame.shape[1] - text_size[0]) // 2
    text_y = (frame.shape[0] + text_size[1]) // 2
    
    cv2.putText(frame, message, (text_x, text_y), font, font_scale, color, thickness)
    
    return frame

# LLM配置相关路由
@main.route('/llm-config')
def llm_config():
    """LLM配置页面"""
    return render_template('llm_config.html')

@main.route('/api/llm-configs', methods=['GET'])
def api_get_llm_configs():
    """获取所有LLM配置"""
    try:
        configs = LLMConfig.query.order_by(LLMConfig.created_at.desc()).all()
        config_list = []
        for config in configs:
            config_list.append({
                'id': config.id,
                'name': config.name,
                'base_url': config.base_url,
                'model': config.model,
                'is_active': config.is_active,
                'created_at': config.created_at.isoformat()
            })
        return jsonify({'success': True, 'configs': config_list})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/api/llm-configs', methods=['POST'])
def api_create_llm_config():
    """创建LLM配置"""
    try:
        data = request.get_json()
        
        # 验证必填字段
        required_fields = ['name', 'base_url', 'model']
        for field in required_fields:
            if not data.get(field):
                return jsonify({'success': False, 'message': f'{field} 是必填字段'}), 400
        
        # api_key 对于本地模型（如Ollama）可以为空
        api_key = data.get('api_key', '')
        
        # 创建新配置
        config = LLMConfig(
            name=data['name'],
            base_url=data['base_url'],
            api_key=api_key,
            model=data['model']
        )
        
        db.session.add(config)
        db.session.commit()
        
        return jsonify({'success': True, 'message': '配置创建成功', 'config_id': config.id})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/api/llm-configs/<int:config_id>', methods=['GET'])
def api_get_llm_config(config_id):
    """获取单个LLM配置"""
    try:
        config = LLMConfig.query.get_or_404(config_id)
        return jsonify({
            'success': True,
            'config': {
                'id': config.id,
                'name': config.name,
                'base_url': config.base_url,
                'api_key': config.api_key,
                'model': config.model,
                'is_active': config.is_active
            }
        })
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/api/llm-configs/<int:config_id>', methods=['PUT'])
def api_update_llm_config(config_id):
    """更新LLM配置"""
    try:
        config = LLMConfig.query.get_or_404(config_id)
        data = request.get_json()
        
        # 更新字段
        if 'name' in data:
            config.name = data['name']
        if 'base_url' in data:
            config.base_url = data['base_url']
        if 'api_key' in data:
            config.api_key = data['api_key']
        if 'model' in data:
            config.model = data['model']
        
        config.updated_at = datetime.utcnow()
        db.session.commit()
        
        return jsonify({'success': True, 'message': '配置更新成功'})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/api/llm-configs/<int:config_id>', methods=['DELETE'])
def api_delete_llm_config(config_id):
    """删除LLM配置"""
    try:
        config = LLMConfig.query.get_or_404(config_id)
        db.session.delete(config)
        db.session.commit()
        
        return jsonify({'success': True, 'message': '配置删除成功'})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/api/llm-configs/<int:config_id>/activate', methods=['POST'])
def api_activate_llm_config(config_id):
    """激活LLM配置"""
    try:
        # 先将所有配置设为非激活状态
        LLMConfig.query.update({'is_active': False})
        
        # 激活指定配置
        config = LLMConfig.query.get_or_404(config_id)
        config.is_active = True
        config.updated_at = datetime.utcnow()
        
        db.session.commit()
        
        return jsonify({'success': True, 'message': '配置激活成功'})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)}), 500

@main.route('/api/llm-configs/active', methods=['GET'])
def api_get_active_llm_config():
    """获取当前激活的LLM配置"""
    try:
        config = LLMConfig.query.filter_by(is_active=True).first()
        if config:
            return jsonify({
                'success': True,
                'config': {
                    'id': config.id,
                    'name': config.name,
                    'base_url': config.base_url,
                    'api_key': config.api_key,
                    'model': config.model
                }
            })
        else:
            return jsonify({'success': False, 'message': '没有激活的LLM配置'}), 404
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500



@main.route('/api/llm/config/active', methods=['GET'])
def get_active_llm_config():
    """获取当前激活的LLM配置"""
    try:
        config = LLMConfig.query.filter_by(is_active=True).first()
        if config:
            return jsonify({
                'success': True,
                'config': {
                    'id': config.id,
                    'name': config.name,
                    'base_url': config.base_url,
                    'model': config.model
                }
            })
        else:
            return jsonify({
                'success': False,
                'message': '没有激活的LLM配置'
            })
    except Exception as e:
        import traceback
        error_details = traceback.format_exc()
        print(f"LLM检测API错误: {e}")
        print(f"错误详情: {error_details}")
        return jsonify({
            'success': False,
            'message': str(e)
        })

@main.route('/api/project/<int:project_id>/upload_model', methods=['POST'])
def upload_model(project_id):
    """上传用户模型文件"""
    try:
        # 检查项目是否存在
        project = Project.query.get_or_404(project_id)
        
        # 检查是否有文件上传
        if 'model_file' not in request.files:
            return jsonify({
                'success': False,
                'error': '没有选择文件'
            })
        
        file = request.files['model_file']
        if file.filename == '':
            return jsonify({
                'success': False,
                'error': '没有选择文件'
            })
        
        # 检查文件扩展名
        allowed_extensions = {'.pt', '.pth', '.onnx'}
        file_ext = os.path.splitext(file.filename)[1].lower()
        if file_ext not in allowed_extensions:
            return jsonify({
                'success': False,
                'error': f'不支持的文件格式。支持的格式: {", ".join(allowed_extensions)}'
            })
        
        # 创建上传目录
        upload_dir = os.path.join('projects', str(project_id), 'uploaded_models')
        os.makedirs(upload_dir, exist_ok=True)
        
        # 生成安全的文件名
        filename = secure_filename(file.filename)
        # 如果文件已存在，添加时间戳
        if os.path.exists(os.path.join(upload_dir, filename)):
            name, ext = os.path.splitext(filename)
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f"{name}_{timestamp}{ext}"
        
        # 保存文件
        file_path = os.path.join(upload_dir, filename)
        file.save(file_path)
        
        return jsonify({
            'success': True,
            'filename': filename,
            'message': '模型上传成功'
        })
        
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        })

@main.route('/api/project/<int:project_id>/uploaded_models', methods=['GET'])
def get_uploaded_models(project_id):
    """获取已上传的模型列表"""
    try:
        # 检查项目是否存在
        project = Project.query.get_or_404(project_id)
        
        # 使用InferenceManager获取已上传的模型列表
        inference_manager = InferenceManager(project_id)
        models = inference_manager.get_uploaded_models()
        
        return jsonify({
            'success': True,
            'models': models
        })
        
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e),
            'models': []
        }), 500

@main.route('/api/project/<int:project_id>/ai-assist', methods=['GET'])
def api_get_project_ai_assist(project_id):
    """获取项目的AI辅助标注设置"""
    project = Project.query.get_or_404(project_id)
    return jsonify({
        'ai_assist_enabled': project.ai_assist_enabled
    })

@main.route('/api/project/<int:project_id>/ai-assist', methods=['PUT'])
def api_update_project_ai_assist(project_id):
    """更新项目的AI辅助标注设置"""
    project = Project.query.get_or_404(project_id)
    data = request.get_json()
    
    if 'ai_assist_enabled' in data:
        project.ai_assist_enabled = bool(data['ai_assist_enabled'])
        db.session.commit()
        
        return jsonify({
            'success': True,
            'ai_assist_enabled': project.ai_assist_enabled
        })
    
    return jsonify({'error': '缺少必要参数'}), 400

@main.route('/api/llm/detect', methods=['POST'])
def llm_detect():
    """LLM检测接口"""
    try:
        data = request.get_json()
        project_id = data.get('project_id')
        image_data = data.get('image_data')
        
        if not project_id or not image_data:
            return jsonify({
                'success': False,
                'message': '缺少必要参数'
            }), 400
        
        # 获取项目和标签
        project = Project.query.get_or_404(project_id)
        labels = Label.query.filter_by(project_id=project_id).all()
        
        if not labels:
            return jsonify({
                'success': False,
                'message': '项目中没有标签，请先创建标签'
            }), 400
        
        # 使用LLM服务进行检测
        from services.llm_service import LLMService
        llm_service = LLMService()
        
        # 使用新的base64检测方法
        detections = llm_service.detect_objects_from_base64(image_data, labels)
        
        return jsonify({
            'success': True,
            'detections': detections
        })
        
    except Exception as e:
        return jsonify({
            'success': False,
            'message': str(e)
        }), 500
